package org.nahtanoj.driveutils;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

import com.google.api.client.http.FileContent;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Files;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import com.google.api.services.drive.model.ParentReference;

public class DriveUtils {
	private static HttpTransport httpTransport = new NetHttpTransport();
	private static JsonFactory jsonFactory = new JacksonFactory();
	private static final Logger log = Logger.getLogger(DriveUtils.class.getName());
	public static final String MIME_AVI = "video/x-msvideo";
	public static final String MIME_DIR = "application/vnd.google-apps.folder";
	public static final String MIME_JPG = "image/jpeg";
	public static final String MIME_MOV = "video/quicktime";
	private static final int RETRIES = 3;
	private final Authenticator a = new Authenticator();
	private final Drive d;
	private final String rootFolderId;

	public DriveUtils() throws IOException {
		this.d = new Drive.Builder(httpTransport, jsonFactory, a.getCredential()).build();
		this.rootFolderId = retryGetRootId();
	}

	private List<File> driveGetFiles(String query) {
		List<File> result = new ArrayList<File>();
		try {
			Files.List request = this.d.files().list().setQ(query);
			do {
				try {
					FileList files = request.execute();
					result.addAll(files.getItems());
					request.setPageToken(files.getNextPageToken());
				} catch (IOException e) {
					log.severe("Query:" + query + " " + e.getMessage());
					request.setPageToken(null);
					result = null;
				}
			} while (request.getPageToken() != null &&
					request.getPageToken().length() > 0);
		} catch (IOException e) {
			log.severe("Query:" + query + " " + e.getMessage());
			result = null;
		}
		return result;
	}

	private String driveGetRootId() {
		String rootId = null;
		try {
			rootId = this.d.about().get().execute().getRootFolderId();
		} catch (IOException e) {
			log.severe(e.getMessage());
		}
		return rootId;
	}

	private String driveNewFile(String filename, String parentId) {
		String id = null;
		File body = new File();
		String mime = MIME_JPG;
		body.setTitle(new java.io.File(filename).getName());
		if (filename.toLowerCase().endsWith("avi")) {
			mime = MIME_AVI;
		}
		if (filename.toLowerCase().endsWith(".mov")) {
			mime = MIME_MOV;
		}
		body.setMimeType(mime);

		if (parentId != null && parentId.length() > 0) {
			body.setParents(Arrays.asList(new ParentReference().setId(parentId)));
		}

		java.io.File fileContent = new java.io.File(filename);
		FileContent mediaContent = new FileContent(mime, fileContent);

		try {
			Drive.Files.Insert request = this.d.files().insert(body, mediaContent);
			request.getMediaHttpUploader().setProgressListener(new CustomProgressListener());
			File file = request.execute();
			id = file.getId();
		} catch (IOException e) {
			log.severe("Filename:" + filename + " ParentId:" + parentId + " " + e.getMessage());
		}
		return id;
	}

	private String driveNewFolder(String folder, String parentId) {
		String id = null;
		File body = new File();
		body.setTitle(folder);
		body.setMimeType(MIME_DIR);
		body.setParents(Arrays.asList(new ParentReference().setId(parentId)));
		try {
			File file = this.d.files().insert(body).execute();
			id = file.getId();
		} catch (IOException e) {
			log.severe("Folder:" + folder + " ParentId:" + parentId + " " + e.getMessage());
		}
		return id;
	}

	private boolean driveRemoveId(String fileId) {
		boolean result = false;
		try {
			this.d.files().delete(fileId).execute();
		} catch (IOException e) {
			log.severe("FileId:" + fileId + " " + e.getMessage());
		}
		return result;
	}

	/**
	 * @param filename - file to locate
	 * @param actualPrefix - Google Drive prefix
	 * @return Google Drive File ID, null if not found or empty string if multiple files found
	 */
	private String getFileId(String filename, String actualPrefix) {
		String fileId = null;
		java.io.File f = new java.io.File(actualPrefix + "/" + filename);
		String parentId = getFolderId(f.getParent());
		if (parentId != null) {
			List<File> files = retryGetFiles("'" + parentId + "' in parents and Title = '" + f.getName() + "'");
			if (files != null && files.size() == 1) {
				fileId = files.get(0).getId();
			}
		}
		return fileId;
	}
	/**
	 * Attempts to find the ID for the folder path given
	 * @param folder - folder path to attempt to find
	 * @return Google Drive ID if the path is found - null if not
	 */
	private String getFolderId(String folderName) {
		// Find folder
		String parentId = this.rootFolderId;
		java.io.File f = new java.io.File(folderName);
		List<String>folders = new ArrayList<String>();
		// Loop while we have a parent and the parent is not root
		while (f.getParent() != null && !f.getParent().equals(java.io.File.separator)) {
			folders.add(f.getName());
			f = f.getParentFile();
		}
		if (f.getName().length() > 0) {
			folders.add(f.getName());
		}
		Collections.reverse(folders);

		for (String folder : folders) {
			if (parentId != null) {
				List<File>files = retryGetFiles("'" + parentId + "' in parents and mimeType = '" + MIME_DIR + "' and title='" + folder + "' and trashed=false");
				if (files == null || files.size() != 1) {
					// There was an error
					parentId = null;
				} else {
					parentId = files.get(0).getId();
				}
			}
		}
		return parentId;
	}

	private void getFolderMeta(FileWriter fw, String parentId, String path) throws IOException {
		log.info("Processing:" + path);
		List<File>files = retryGetFiles("'" + parentId + "' in parents and trashed=false");
		for (File f : files) {
			if (f.getMimeType().equals(MIME_DIR)) {
				getFolderMeta(fw, f.getId(), new java.io.File(path, f.getTitle()).getPath());
			} else {
				fw.write(f.getFileSize().toString());
				fw.write(" ");
				fw.write(new java.io.File(path, f.getTitle()).getPath());
				fw.write("\n");
			}
		}
	}

	public void getFolderMeta(String folder, String filename) {
		String parentId = getFolderId(folder);
		if (parentId != null) {
			try {
				FileWriter fw = new FileWriter(filename);
				getFolderMeta(fw, parentId, folder);
				fw.close();
			} catch (IOException e) {
				log.severe("Folder:" + folder + " Filename:" + filename + " " + e.getMessage());
			}
		} else {
			log.warning("Cannot locate folder:" + folder);
		}
	}

	public boolean newFile(String filename, String actualPrefix, String masterPrefix) {
		boolean result = false;
		// If file exists delete it and then upload
		String fileId = getFileId(actualPrefix, filename);
		if (fileId != null) {
			log.info("Deleting:" + filename + " actualPrefix:" + actualPrefix + " masterPrefix:" + masterPrefix);
			if (retryRemoveId(fileId) == true) {
				fileId = null;
			} else {
				log.warning("Unable to remove Filename:" + filename + " actualPrefix:" + actualPrefix + " masterPrefix:" + masterPrefix);
			}
		}
		if (fileId == null) {
			String parentId = newFolder(new java.io.File(actualPrefix + "/" + filename).getParent());
			if (parentId != null) {
				log.info("Uploading:" + masterPrefix + "/" + filename);
				String id = retryNewFile(masterPrefix + filename, parentId);
				if (id != null) {
					result = true;
				} else {
					log.warning("Problem uploading Filename:" + filename + " ActualPrefix:" + actualPrefix + " masterPrefix:" + masterPrefix);
				}
			} else {
				log.warning("Problem making folders Filename:" + filename + " ActualPrefix:" + actualPrefix + " masterPrefix:" + masterPrefix);
			}
		}
		return result;
	}

	private String newFolder(String folder) {
		String folderId;
		if (folder == null || folder.equals(java.io.File.separator)) {
			folderId = this.rootFolderId;
		} else {
			folderId = getFolderId(folder);
			if (folderId == null) {
				folderId = newFolder(new java.io.File(folder).getParent());
				folderId = retryNewFolder(new java.io.File(folder).getName(), folderId);
			}
		}
		return folderId;
	}

	private List<File> retryGetFiles(String query) {
		int retry = 1;
		List<File>files = driveGetFiles(query);
		while (files == null && retry < RETRIES) {
			retry = retry + 1;
			log.warning("Try:" + retry + " Query:" + query);
			files = driveGetFiles(query);
		}
		return files;
	}

	private String retryGetRootId() {
		int retry = 1;
		String id = driveGetRootId();
		while (id == null && retry < RETRIES) {
			retry = retry + 1;
			log.warning("Try:" + retry);
			id = driveGetRootId();
		}
		return id;
	}

	private String retryNewFile(String filename, String parentId) {
		int retry = 1;
		String id = driveNewFile(filename, parentId);
		while (id == null && retry < RETRIES) {
			retry = retry + 1;
			log.warning("Try:" + retry + " Filename:" + filename + " ParentId:" + parentId);
			id = driveNewFile(filename, parentId);
		}
		return id;
	}

	private String retryNewFolder(String name, String parentId) {
		int retry = 1;
		String id = driveNewFolder(name, parentId);
		while (id == null && retry < RETRIES) {
			retry = retry + 1;
			log.warning("Try:" + retry + " Name:" + name + " ParentId:" + parentId);
			id = driveNewFolder(name, parentId);
		}
		return id;
	}

	private boolean retryRemoveId(String fileId) {
		int retry = 1;
		boolean result = driveRemoveId(fileId);
		while (result == false && retry < RETRIES) {
			retry = retry + 1;
			log.warning("Try:" + retry + " FileId:" + fileId);
			result = driveRemoveId(fileId);
		}
		return result;
	}
}
